#undef VERBOSE
#ifdef VERBOSE
#define IF_VERBOSE(x) ({x;})
#else
#define IF_VERBOSE(x)
#endif



#include <SPI.h>
#include <RF24.h>
#include <RF24Network.h>
#include "S_message.h"
#include <printf.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "EmonLib.h" 
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <string.h>
#include <PString.h>
#include <EEPROM.h>





//pins for radio
const uint8_t rf_ce = 6;
const uint8_t rf_csn = 5;

//radio channel
const uint8_t radio_channel = 92;


//Callibration button pin
const uint8_t callibrationButton = 9;

//DS18B20 pin and temp precision
const uint8_t one_wire = 8;
const uint8_t temp_precision = 9;

//we will force reportinng every time 'cycle' reaches 'report_cycles'
const uint16_t report_cycles = 100; // 1 hour
uint16_t cycle = 0;

const unsigned long scan_timeout = 60000;
unsigned long scan_timer;


//set up the temp sensor
OneWire oneWire(one_wire);
DallasTemperature tempSensors(&oneWire);
DeviceAddress tempDeviceAddress;


//set up radio
RF24 radio (rf_ce, rf_csn);
RF24Network network (radio);
RF24NetworkHeader header(/*to node*/ 0, /*type*/ 'S'); //header for the send message
S_message message;
S_message refMessage;
const uint16_t my_address = 01;
uint8_t defMask;

bool ok;
bool debounce;
bool callibrationON;


//set up current sensor
EnergyMonitor emon1; 
const int current_sensor_pin = 1;   //pin A1
const double Ical = 32.4;
//double Irms = 0;
const int Irms_samples = 1480;
const double Vrms = 230.0;
const double deltaUploadPower = 5.0;
const double smoothness = 0.3;
double power_reading = 0.0;
int callibrationOffset;




//set up LCD
const short  lcdSize = 16; // What is the size of our screen , this could probably be moved outside the loop but its more dynamic like this
LiquidCrystal_I2C lcd(0x27,lcdSize,2);  //set the lcd address to 0x27
int pos = 0;
short okPosition;
char lcdBuffer[lcdSize];//Create a char array to store the text for the line
char printString[32];
PString printer(printString, sizeof(printString));
unsigned long lcd_update_timer = 0;
unsigned long lcd_update_timeout = 100; //100 milliseconds



void setup(){
  IF_VERBOSE(Serial.begin(57600));
  IF_VERBOSE(Serial.println("Start..."));
  IF_VERBOSE(printf_begin());
  lcd.init();                      // initialize the lcd 
  lcd.backlight();
  lcd.clear();
  lcd.print("starting...");

  
  radio.begin();
  network.begin (radio_channel, my_address);
  
  tempSensors.begin();
  
  callibrationON = false;
  emon1.current(current_sensor_pin, Ical);
  pinMode(callibrationButton, INPUT_PULLUP);
  if (EEPROM.read(0) == 255)
    callibrationOffset = EEPROM.read(1);
  else
    callibrationOffset = 0;
    
 
  
  //define active sensors
  defMask = 0;
  bitSet(defMask, S_MASK_POWER);
  bitSet(defMask, S_MASK_BATTERY);
  bitSet(defMask, S_MASK_TEMP);
  
  message.mask = defMask;
  
  okPosition = 5;
  lcd.clear();
  lcd.setCursor(0,1);
  lcd.print("Link: "); 
  

  
}


void printLine(){
 
  unsigned short copySize = lcdSize;
  unsigned short stringSize = strlen(printString);
  
    lcd.setCursor(0, 0);//Set our draw position , set second param to 0 to use the top line
    
    if(stringSize < lcdSize)
    {
        //if the message is bigger than the current buffer use its length instead;
        copySize = stringSize;
    }
    //Store the current position temporarily and invert its sign if its negative since we are going in reverse
    int tempPos = pos;
    if(tempPos < 0)
    {
        tempPos = -(tempPos);
    }
    //Build the lcd text by copying the required text out of our template message variable 
    memcpy(lcdBuffer,&printString[tempPos],copySize);
    //lcdBuffer[lcdSize] = 0; //Serial.println(lcdBuffer);
    lcd.print(lcdBuffer);//Print it from position 0
    
    //Increase the current position and check if the position + 16 (screen size) would be larger than the message length , if it is go in reverse by inverting the sign.
    if (stringSize > lcdSize) //in the origignal code this line is missing
      pos += 1;
    else
      pos = 0;
      
    if(pos + lcdSize >= stringSize)
    {
      pos = -(pos);
    }
    
}

float smooth(float data, float filterVal, float smoothedVal){


  if (filterVal >= 1){      // check to make sure param's are within range
    filterVal = .9999;
  }
  else if (filterVal <= 0){
    filterVal = 0;
  }

  smoothedVal = (data * (1 - filterVal)) + (smoothedVal  *  filterVal);

  return smoothedVal;
}






void loop(){
  
  // Pump the network regularly
  network.update();
  
  
  if (millis() > lcd_update_timer){
    lcd_update_timer = millis()+lcd_update_timeout;
    printLine();
  }
  
  if (digitalRead(callibrationButton) == LOW){
     lcd.setCursor(9,1);
     lcd.print(callibrationOffset);
     
     if (debounce == HIGH){
       delay(5000);
       if (digitalRead(callibrationButton) == LOW ){
         debounce = LOW;
         callibrationON = true;
         lcd.setCursor(8,1);
         lcd.print("*");

       }
     }
  }
  else{
     debounce = HIGH;
     if (callibrationON){
         callibrationOffset = power_reading; 
         callibrationON = false;
         EEPROM.write(0, 255);
         EEPROM.write(1, (byte)callibrationOffset);
         lcd.setCursor(15, 1);
         lcd.print("+");
         delay(1000); 
     }
     callibrationON = false;
     lcd.setCursor(8,1);
     lcd.print("        ");
  }
      
    //measure power
    //message.power_reading = Vrms*emon1.calcIrms(Irms_samples);
    
    power_reading =  smooth(Vrms*emon1.calcIrms(Irms_samples), smoothness, power_reading);
    if (power_reading >= callibrationOffset){
      message.power_reading = power_reading - callibrationOffset;
      lcd.setCursor(15,1);
      lcd.print(" ");
    }
    else{
      message.power_reading = 0;
      lcd.setCursor(15,1);
      lcd.print("-");
    }
      
    if (abs(refMessage.power_reading-message.power_reading) > deltaUploadPower)
      bitSet(message.mask, S_MASK_POWER);
    
    //measure temperature
    tempSensors.requestTemperatures();
    message.temp_reading = tempSensors.getTempCByIndex(0);
    if (refMessage.temp_reading != message.temp_reading)
      bitSet(message.mask, S_MASK_TEMP);
    
    //reading battery Vcc
    message.battery_reading = emon1.readVcc();
    if (refMessage.battery_reading != message.battery_reading)
      bitSet(message.mask, S_MASK_BATTERY);
      

  printer.begin();
  printer.print("Power: ");
  if (digitalRead(callibrationButton) == HIGH)
    printer.print(message.power_reading);
  else
    printer.print(power_reading);
  printer.print("Wh; Temp: ");
  printer.print(message.temp_reading);
  printer.print("C ");


  if (millis() >scan_timer){
    
    scan_timer = millis()+scan_timeout;
    cycle++;

      
    if (cycle>report_cycles){
      message.mask = defMask; //force report
      cycle = 0; //reset timer
    }
    
    
    if (message.mask){
      IF_VERBOSE(printf("%lu:cycle %u, sending %s", millis(), cycle, message.toString()));
      
      ok=network.write(header,&message,sizeof(message));
      
      if (ok) {
        IF_VERBOSE(printf("...OK\n"));
        refMessage = message;
        lcd.setCursor(okPosition, 1);
        lcd.print("OK ");
      }
      else{
        IF_VERBOSE(printf("...NOK\n"));
        lcd.setCursor(okPosition, 1);
        lcd.print("NOK");
        //as sending measurements during this force_report cycle failed, 
        //make sure next cycle is again force_report
        if (message.mask == defMask) cycle = report_cycles;
      }  
    }
  } 
  message.mask = 0;
}

